# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from functools import wraps

from flask import session, request, jsonify, url_for, abort, redirect, Blueprint, g, current_app, abort
from flask_oauthlib.client import OAuth
from flask_oauthlib.contrib.apps import google

from data.database import DEFAULT_DATABASE
from data.models import User

bp = Blueprint('auth', __name__, url_prefix='/auth')
db = DEFAULT_DATABASE.db

oauth = OAuth()
google = google.register_to(oauth, name='GOOGLE_OAUTH')


@bp.record
def init_auth(state):
  oauth.init_app(state.app)


@bp.route('/login', methods=['GET'])
def login():
  if is_authenticated():
    return redirect('/')
  return google.authorize(callback=url_for('auth.authorized', _external=True))


@bp.route('/logout', methods=['GET'])
def logout():
  session.clear()
  return redirect('/')


@bp.route('/authorized')
def authorized():
  try:
    resp = google.authorized_response()
  except:
    current_app.logger.exception('Error during handling the oauth response')
    abort(400)

  if resp is None:
    error_message = 'Access denied'
    if 'error_reason' in request.args:
      error_message += ': reason=%s error=%s' % (
          request.args['error_reason'], request.args['error_description'])
    return error_message
  # don't leak access_token into the session cookie
  # session['google_token'] = (resp['access_token'], '')
  do_redirect = session.pop('redirect_path', None)
  me = google.get('userinfo', token=resp['access_token']).data

  session['user_info'] = me

  if do_redirect:
    return redirect(do_redirect)

  return redirect('/')


def is_admin():
  if is_authenticated():
    email = session['user_info']['email']
    if email in current_app.config['APPLICATION_ADMINS']:
      return True
  return False


@bp.before_app_request
def load_user():
  user = None

  # Ignore all non-admin users for now.
  if not is_admin():
    g.user = None
    return

  if is_authenticated():
    data = session['user_info']
    email = data['email']
    user = User.query.filter_by(email=email).one_or_none()
    if not user:
      user = User(
          email=email, full_name=data['name'], profile_picture=data['picture'])
    else:
      user.full_name = data['name']
      user.profile_picture = data['picture']
    db.session.add(user)
    db.session.commit()

  g.user = user


@google.tokengetter
def get_google_oauth_token():
  return session.get('google_token')


def is_authenticated():
  if 'user_info' in session:
    return True

  if 'google_token' not in session:
    return False

  data = google.get('userinfo').data
  if data.get('error', False):
    del session['google_token']
    return False

  session['user_info'] = data

  return True


def login_required(redirect=False):

  def decorator(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
      if not is_authenticated():
        if redirect:
          session['redirect_path'] = request.full_path
          return google.authorize(
              callback=url_for('auth.authorized', _external=True))
        else:
          return abort(401)
      return func(*args, **kwargs)

    return wrapper

  return decorator


def admin_required(redirect=False):

  def decorator(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
      if not is_admin():
        if redirect:
          session['redirect_path'] = request.full_path
          return google.authorize(
              callback=url_for('auth.authorized', _external=True))
        else:
          return abort(401)
      return func(*args, **kwargs)

    return wrapper

  return decorator
